/**
 * 
 */
package org.apache.james.hibernate;

import javax.mail.Flags;

import org.apache.james.mailboxmanager.GeneralMessageSet;
import org.apache.james.mailboxmanager.MailboxListener;
import org.apache.james.mailboxmanager.MailboxManagerException;
import org.apache.james.mailboxmanager.MessageResult;
import org.apache.james.mailboxmanager.impl.GeneralMessageSetImpl;
import org.apache.james.mailboxmanager.impl.MailboxEventDispatcher;
import org.apache.james.mailboxmanager.mailbox.AbstractGeneralMailbox;
import org.apache.james.mailboxmanager.mailbox.ImapMailbox;
import org.apache.james.mailboxmanager.tracking.UidChangeTracker;
import org.apache.james.mailboxmanager.tracking.UidRange;
import org.apache.james.mailboxmanager.util.UidToKeyConverter;
import org.apache.james.mailboxmanager.util.UidToKeyConverterImpl;

/**
 * @author zsombor
 * 
 */
public abstract class AbstractImapMailbox extends AbstractGeneralMailbox
		implements ImapMailbox {

	private MailboxEventDispatcher eventDispatcher;
	private UidChangeTracker tracker;
	private boolean open = true;
	private Flags permanentFlags;
	private UidToKeyConverterImpl uidToKeyConverter;

	/**
	 * 
	 */
	public AbstractImapMailbox(UidChangeTracker tracker, long uidValidity) {
		this.tracker = tracker;
		tracker.addMailboxListener(getEventDispatcher());
		getUidToKeyConverter().setUidValidity(uidValidity);
	}

	/**
	 * return the last UID of the stored mailbox.
	 * 
	 * @return
	 */
	protected abstract long getMailboxLastUid();

	/**
	 * check that the mailbox still exists in the database, and refetch the
	 * values from.
	 * 
	 * @return
	 */
	protected abstract boolean checkMailboxExists();

	@Override
	public int getMessageResultTypes() {
		return MessageResult.FLAGS + MessageResult.INTERNAL_DATE
				+ MessageResult.KEY + MessageResult.MIME_MESSAGE
				+ MessageResult.SIZE + MessageResult.UID;
	}

	@Override
	public int getMessageSetTypes() {
		return GeneralMessageSet.TYPE_ALL + GeneralMessageSet.TYPE_KEY
				+ GeneralMessageSet.TYPE_UID + GeneralMessageSet.TYPE_MESSAGE;
	}

	protected MailboxEventDispatcher getEventDispatcher() {
		if (eventDispatcher == null) {
			eventDispatcher = new MailboxEventDispatcher();
		}
		return eventDispatcher;
	}

	protected void checkAccess() throws MailboxManagerException {
		if (!open) {
			throw new RuntimeException("mailbox is closed");
		} else if (getEventDispatcher().size() == 0) {
			throw new RuntimeException("mailbox has not been opened");
		} else if (getUidChangeTracker().isExisting()) {
			throw new MailboxManagerException("Mailbox is not existing");
		}
	}

	@Override
	public synchronized Flags getPermanentFlags() {
		if (permanentFlags == null) {
			permanentFlags = new Flags();
			permanentFlags.add(Flags.Flag.ANSWERED);
			permanentFlags.add(Flags.Flag.DELETED);
			permanentFlags.add(Flags.Flag.DRAFT);
			permanentFlags.add(Flags.Flag.FLAGGED);
			permanentFlags.add(Flags.Flag.RECENT);
			permanentFlags.add(Flags.Flag.SEEN);
		}
		return permanentFlags;
	}

	protected UidChangeTracker getUidChangeTracker() {
		return tracker;
	}

	@Override
	public void addListener(MailboxListener listener, int result)
			throws MailboxManagerException {
		getEventDispatcher().addMailboxListener(listener);
		checkAccess();
	}

	@Override
	public void removeListener(MailboxListener mailboxListener) {
		if (!open || getEventDispatcher().size() == 0) {
			throw new RuntimeException("mailbox not open");
		}
		getEventDispatcher().removeMailboxListener(mailboxListener);
		if (getEventDispatcher().size() == 0) {
			open = false;
			getUidChangeTracker().removeMailboxListener(getEventDispatcher());
		}
	}

	protected UidToKeyConverter getUidToKeyConverter() {
		if (uidToKeyConverter == null) {
			uidToKeyConverter = new UidToKeyConverterImpl();
		}
		return uidToKeyConverter;
	}

	public void remove(GeneralMessageSet set) throws MailboxManagerException {
		setFlags(new Flags(Flags.Flag.DELETED), true, false, set, null);
		expunge(set, MessageResult.NOTHING);
	}

	@Override
	public void setFlags(Flags flags, boolean value, boolean replace,
			GeneralMessageSet set, MailboxListener silentListener) throws MailboxManagerException {
        checkAccess();
        set=toUidSet(set);  
        if (!set.isValid() || set.getType()==GeneralMessageSet.TYPE_NOTHING) {
            return;
        }        

        final MessageResult[] beforeResults = getMessages(set, MessageResult.UID | MessageResult.FLAGS);
        UidRange uidRange=uidRangeForMessageSet(set);
        checkForScanGap(uidRange.getFromUid());
        getUidChangeTracker().found(uidRange, beforeResults, null);

        setFlags(flags, value, replace, beforeResults,set);

        final MessageResult[] afterResults = getMessages(set, MessageResult.UID | MessageResult.FLAGS);
        tracker.found(uidRange, afterResults, silentListener);

	}

	/**
	 * This method should apply the flags to the persistent storage.
	 * 
	 * @param flags The flag objects
	 * @param addFlags if replace is false, then the given flags should be 'added' or 'removed' from mails.
	 * @param replace if replace is true, then the mail flags should be replaced with the given ones.
	 * @param messageList The list of the messages, as an array of MessageResult objects.
	 * @param set The list of the messages, as a GeneralMessageSet.
	 * @throws MailboxManagerException 
	 */
	protected abstract void setFlags(Flags flags, boolean addFlags, boolean replace,
			MessageResult[] messageList, GeneralMessageSet set) throws MailboxManagerException;

	@Override
	public final synchronized long getUidNext() throws MailboxManagerException {
		checkAccess();

		if (checkMailboxExists()) {
			getUidChangeTracker().foundLastUid(getMailboxLastUid());
			return getUidChangeTracker().getLastUid() + 1;
		} else {
			getUidChangeTracker().mailboxNotFound();
			throw new MailboxManagerException("Mailbox has been deleted");
		}
	}

	protected GeneralMessageSet toUidSet(GeneralMessageSet set) {
		if (set.getType() == GeneralMessageSet.TYPE_KEY) {
			Long uid = getUidToKeyConverter().toUid(set.getKey());
			if (uid != null) {
				set = GeneralMessageSetImpl.oneUid(uid.longValue());
			} else {
				set = GeneralMessageSetImpl.nothing();
			}
		}
		return set;
	}
    static UidRange uidRangeForMessageSet(GeneralMessageSet set)
			throws MailboxManagerException {
		if (set.getType() == GeneralMessageSet.TYPE_UID) {
			return new UidRange(set.getUidFrom(), set.getUidTo());
		} else if (set.getType() == GeneralMessageSet.TYPE_ALL) {
			return new UidRange(1, -1);
		} else {
			throw new MailboxManagerException("unsupported MessageSet: "
					+ set.getType());
		}
	}
    protected void checkForScanGap(long uid) throws MailboxManagerException {
        synchronized (getUidChangeTracker()) {
            long lastScannedUid=getUidChangeTracker().getLastScannedUid();
            if (uid>(lastScannedUid+1)) {
                GeneralMessageSet set=GeneralMessageSetImpl.uidRange(lastScannedUid+1, uid);
                MessageResult[] messageResults=getMessages(set, MessageResult.UID);
                getUidChangeTracker().found(uidRangeForMessageSet(set), messageResults, null);
            }
        }
        
    }

}
